import logging
from logging.config import fileConfig

from flask import current_app

from alembic import context

# 导入日志模块，用于记录 alembic 执行过程中的信息
# 从日志配置模块中导入 fileConfig 函数，用于读取日志配置文件
# 从 flask 导入 current_app，它代表当前运行的 Flask 应用实例
# 从 alembic 导入 context，它是 alembic 的上下文对象，用于管理数据库迁移操作

# 这是 Alembic Config 对象，提供对 .ini 文件中值的访问
config = context.config

# 解释配置文件以进行 Python 日志记录，这行代码基本上设置了日志记录器
fileConfig(config.config_file_name)
logger = logging.getLogger('alembic.env')

def get_engine():
    """
    获取数据库引擎实例。

    尝试通过 Flask-SQLAlchemy 旧版本（<3）和 Alchemical 的方式获取引擎，
    若失败则使用 Flask-SQLAlchemy 新版本（>=3）的方式获取。

    Returns:
        sqlalchemy.engine.Engine: 数据库引擎实例。
    """
    try:
        # 这种方式适用于 Flask-SQLAlchemy<3 和 Alchemical
        return current_app.extensions['migrate'].db.get_engine()
    except (TypeError, AttributeError):
        # 这种方式适用于 Flask-SQLAlchemy>=3
        return current_app.extensions['migrate'].db.engine

def get_engine_url():
    """
    获取数据库引擎的连接 URL。

    尝试渲染引擎的 URL 并隐藏密码，同时处理百分号转义，
    若渲染失败则直接将 URL 转为字符串并处理转义。

    Returns:
        str: 数据库连接 URL。
    """
    try:
        return get_engine().url.render_as_string(hide_password=False).replace(
            '%', '%%')
    except AttributeError:
        return str(get_engine().url).replace('%', '%%')

# 为 alembic 配置设置 SQLAlchemy 的连接 URL，该 URL 通过 get_engine_url 函数获取
config.set_main_option('sqlalchemy.url', get_engine_url())
# 获取当前 Flask 应用扩展中迁移插件对应的数据库实例
target_db = current_app.extensions['migrate'].db

# 其他从配置中获取的值，可根据 env.py 的需求进行定义
# 例如：
# my_important_option = config.get_main_option("my_important_option")
# ... 等等

def get_metadata():
    """
    获取数据库元数据对象。

    检查目标数据库实例是否有多个元数据字典，若有则返回默认的元数据，
    若没有则返回数据库实例的元数据。

    Returns:
        sqlalchemy.schema.MetaData: 数据库元数据对象。
    """
    if hasattr(target_db, 'metadatas'):
        return target_db.metadatas[None]
    return target_db.metadata

def run_migrations_offline():
    """
    以离线模式运行数据库迁移。

    此模式下仅配置迁移上下文的数据库连接 URL 和目标元数据，
    不创建实际的数据库引擎，适用于生成迁移脚本而不执行迁移操作的场景。

    调用 context.execute() 时会将 SQL 语句输出到脚本文件中。
    """
    # 从配置中获取 SQLAlchemy 的连接 URL
    url = config.get_main_option("sqlalchemy.url")
    # 配置 alembic 迁移上下文，指定连接 URL、目标元数据和字面绑定
    context.configure(
        url=url, target_metadata=get_metadata(), literal_binds=True
    )

    # 开始一个事务块，并在其中运行数据库迁移操作
    with context.begin_transaction():
        context.run_migrations()

def run_migrations_online():
    """
    以在线模式运行数据库迁移。

    此模式下需要创建数据库引擎并建立连接，
    适用于直接在数据库上执行迁移操作的场景。

    包含一个回调函数用于检查是否有数据库模式的变更，若没有则不生成迁移脚本。
    """
    # 该回调函数用于防止在模式没有变化时生成自动迁移
    # 参考：http://alembic.zzzcomputing.com/en/latest/cookbook.html
    def process_revision_directives(context, revision, directives):
        if getattr(config.cmd_opts, 'autogenerate', False):
            script = directives[0]
            if script.upgrade_ops.is_empty():
                directives[:] = []
                logger.info('No changes in schema detected.')

    # 获取当前 Flask 应用扩展中迁移插件的配置参数
    conf_args = current_app.extensions['migrate'].configure_args
    # 若配置参数中没有设置处理修订指令的回调函数，则添加该回调
    if conf_args.get("process_revision_directives") is None:
        conf_args["process_revision_directives"] = process_revision_directives

    # 获取数据库引擎实例
    connectable = get_engine()

    # 建立与数据库的连接，并配置 alembic 迁移上下文
    with connectable.connect() as connection:
        context.configure(
            connection=connection,
            target_metadata=get_metadata(),
            **conf_args
        )

        # 开始一个事务块，并在其中运行数据库迁移操作
        with context.begin_transaction():
            context.run_migrations()

# 根据 alembic 上下文的模式判断是离线还是在线模式，然后执行相应的迁移操作
if context.is_offline_mode():
    run_migrations_offline()
else:
    run_migrations_online()